using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace PRDCollision
{
    public class Node
    {
        public virtual void CollisionWith(Collider guest, Collider master) { }
    }
    public class Collider
    {
        public CollisionManager collisionManager;
        public int xpos;
        public int ypos;
        public int size;
        public Node node;
        public Transform transform;
        public int layer;
        public int[] infos;
        public int index;
        public HashSet<int> guests = new HashSet<int>();
        public bool isMaster;
        public bool isGuest;
        public bool dueAdd = false;
        public bool dueRemove = false;
        public Collider(CollisionManager _collisionManager, Node _node, float _fSize, int _layer, int[] _infos, Transform _transform) 
        {
            collisionManager = _collisionManager;
            node = _node;
            SetSize(_fSize);
            layer = _layer;
            infos = _infos;
            isMaster = layer <= collisionManager.masterBorder;
            transform = _transform;
        }
        public void Disable()
        {
            removeSelf();
        }
        public void DirectRemove()
        {
            dueAdd = false;
            collisionManager.colliders.Remove(this);
            if (isMaster) collisionManager.colliderMasters.Remove(this);
        }
        public void Enable()
        {
            dueRemove = false;
            if (isMaster) dueAdd = true;
            collisionManager.colliders.Add(this);
        }
        public void SetSize(float fSize)
        {
            size = Mathf.CeilToInt(fSize / collisionManager.xUnit) / 2 * 2 + 3;
        }
        public void Update()
        {
            if (transform != null)
            {
                if (dueRemove) return;
                xpos = Mathf.FloorToInt(transform.position.x / collisionManager.xUnit) + collisionManager.xOffset;
                ypos = Mathf.FloorToInt(transform.position.y / collisionManager.yUnit) + collisionManager.yOffset;
                for (int i = 0; i < size; i++)
                {
                    for (int j = 0; j < size; j++)
                    {
                        var x = xpos + i - size / 2;
                        var y = ypos + j - size / 2;
                        if (x > 0 && x < collisionManager.xNum && y > 0 && y < collisionManager.yNum)
                        {
                            if (isMaster) collisionManager.collisionMasters[x, y].Add(this);
                            else collisionManager.collisionGuests[x, y].Add(this);
                        }
                    }
                }
            }
            else removeSelf();
        }
        public void removeSelf()
        {
            dueRemove = true;
        }
        public void AddGuest(int g)
        {
            if (!guests.Contains(g)) 
            {
                guests.Add(g);
            }
        }
        public void HandleCollisions()
        {
            if (dueRemove) return;            
            foreach (var g in guests)
            {
                CollideWith(collisionManager.colliders[g]);
            }
            guests.Clear();
        }
        public void CollideWith(Collider collider)
        {
            node.CollisionWith(collider, this);
        }
    }
    public class CollisionManager
    {
        public int masterBorder = 1;
        public int xNum;
        public int yNum;
        public int xOffset;
        public int yOffset;
        public float xUnit;
        public float yUnit;
        public float width;
        public float height;
        public List<Collider> colliders = new List<Collider>();
        public List<Collider> collidersCopy = new List<Collider>();
        public List<Collider> colliderMasters = new List<Collider>();
        public List<Collider>[,] collisionMasters;
        public List<Collider>[,] collisionGuests;
        public bool[][] layerMask = new bool[][]
        {
            //          0 player bomb -- 1 player bullet -- 2 bullet -- 3 enemy
            new bool[]{     false,            false,         true,        true, },// player bomb

            new bool[]{     false,            false,        false,        true, },// player bullet

            new bool[]{     false,            false,        false,       false, },// bullet

            new bool[]{     false,            false,        false,       false, },// enemy
        };
        public void Init(float _ux, float _uy, float _w, float _h)
        {
            xUnit = _ux;
            yUnit = _uy;
            width = _w;
            height = _h;
            xOffset = Mathf.CeilToInt(width * 0.5f / xUnit);
            yOffset = Mathf.CeilToInt(height * 0.5f / yUnit);
            xNum = xOffset * 2;
            yNum = yOffset * 2;
            collisionMasters = new List<Collider>[xNum, yNum];
            collisionGuests = new List<Collider>[xNum, yNum];
            for (int i = 0; i < xNum; i++)
            {
                for (int j = 0; j < yNum; j++)
                {
                    collisionMasters[i, j] = new List<Collider>();
                    collisionGuests[i, j] = new List<Collider>();
                }
            }
        }
        public Collider AddCollider(Node node, float fSize, int layer, int[] infos, Transform transform)
        {
            var collider = new Collider(this, node, fSize, layer, infos, transform);
            colliders.Add(collider);
            if (collider.isMaster) collider.dueAdd = true;
            return collider;
        }
        public void CheckCollisions()
        {
            int index = 0;
            foreach (Collider collider in colliders)
            {
                collider.Update();
                collider.index = index;
                index++;
                collidersCopy.Add(collider);
                if (collider.dueAdd) 
                {
                    colliderMasters.Add(collider);
                    collider.dueAdd = false;
                }
            }

            for (int i = 0; i < xNum; i++)
            {
                for (int j = 0; j < yNum; j++)
                {
                    var mc = collisionMasters[i, j].Count;
                    for (int m = 0; m < mc; m++)
                    {
                        var gc = collisionGuests[i, j].Count;
                        for (int n = 0; n < gc; n++)
                        {
                            if (layerMask[collisionMasters[i, j][m].layer][collisionGuests[i, j][n].layer]) 
                                collisionMasters[i, j][m].AddGuest(collisionGuests[i, j][n].index);
                        }
                    }
                    collisionMasters[i, j].Clear();
                    collisionGuests[i, j].Clear();
                }
            }

            foreach (Collider collider in colliderMasters)
            {
                collider.HandleCollisions();
            }

            foreach (Collider collider in collidersCopy)
            {
                if (collider.dueRemove) 
                {
                    colliders.Remove(collider);
                    if (collider.isMaster) colliderMasters.Remove(collider);
                }
            }
            collidersCopy.Clear();
        }
    }
}
